<?php

/**
 * Console part of css file, you can manage/compact css files.
 */

$args = ($_SERVER['argv']);

if (count($args) != 0  && $args[0] == 'css.php')
{
    $css =  new StyleSheet();
    if ($args[1] && $args[2])
    {
        $ok = $css -> loadFromUrl($args[1]);
        if ($ok)
        {
            //$name = true, $newLine = false, $align = false , $indentSpaces = 0, $compact = true
            if (in_array('-newline', $args) )
            {
            	console('New line option enabled.');
                $newLine = true;
            }
            if (in_array('-indent', $args) )
            {
            	console('Indent option enabled.');
                $indentSpaces = 1;
            }
            for ($i=0; $i<99; $i++)
            {
                if (in_array("-indent$i", $args) )
                {
                    console("Indent $i option enabled.");
                    $indentSpaces = $i;
                }
            }
            if (in_array('-align', $args) )
            {
            	console('Align option enabled.');
                $align = true;
            }
            $compact = true;
            if (in_array('-nocompact', $args) )
            {
            	console('No compact option enabled.');
                $compact = false;
            }
            $result = $css -> __toString(true, $newLine, $align, $indentSpaces, $compact);
            $css->saveToFile($args[2], $result);
            console('File saved with sucess.');
            $countClasses = count($css->styleClass);
            foreach ($css->styleClass as $line => $class)
            {
                $countElements += count($class->styles);
            }
            console('The result file has ' . $countClasses .' classes and ' . $countElements .' elements.');
        }
        else
        {
            console('File don\'t exists.');
        }
    }
    else
    {
        console('Please pass a path to load the css file and a destination');
        console('Use: php css.php input.css output.css -option1 -option2');
        console('Available options (all lowercase): -newline -indent -indent$i -align -nocompact');
    }
}

function console($string)
{
    echo $string."\n";
}

//TODO: a way to extract coments out of a css

/**
 * Created on 27/09/2008 - (it was a part of html.php code created in 10/02/2008)
 * @author Eduardo Bonfandini
 * @email eduardo@nostaljia.eng.br
 * @email trialforce@gmail.com
 *
 * This is a file/class used to element CSS and styles.
 *
 * It is on initial state, please use carefully, some methods name will change.
 *
 * Cascading Style Sheets (CSS) is a styleSheet language used to describe the presentation of a document
 * written in a markup language. Its most common application is to style web pages written in HTML and
 * XHTML, but the language can be applied to any kind of XML document, including SVG and XUL.

 * CSS can be used locally by the readers of web pages to define colors, fonts, layout, and other
 * aspects of document presentation. It is designed primarily to enable the separation of document
 * content (written in HTML or a similar markup language) from document presentation (written in CSS).
 * This separation can improve content accessibility, provide more flexibility and element in the
 * specification of presentation characteristics, and reduce complexity and repetition in the structural
 * content (such as by allowing for tableless web design). CSS can also allow the same markup page to be
 * presented in different styles for different rendering methods, such as on-screen, in print, by voice
 * (when read out by a speech-based browser or screen reader) and on Braille-based, tactile devices. CSS
 * specifies a priority scheme to determine which style rules apply if more than one rule matchesagainst
 * a particular element. In this so-called cascade, priorities or weights are calculated and assigned to
 * rules, so that the results are predictable.
 *
 * The CSS specifications are maintained by the World Wide Web Consortium (W3C). Internet media type
 * (MIME type) text/css is registered for use with CSS by RFC 2318 (March 1998).
 *
 * Cascading Style Sheet Examples:
 *
 * .classA
 * {
 *     position    : relative;
 *     top         : 50px;
 *     left        : 30px;
 *     margin-left : 2px;
 *     margin-top  : 3px;
 * }
 *
 * .classB
 * {
 * position:relative;
 * top:50px;
 * left:30px;
 * margin-left:2px;
 * margin-top:3px;
 * }
 *
 * .classC{color:blue;text-align:center;text-decoration:none;}
 *
 * .classD{color:red;text-align:right;text-decoration:underline;}
 *
 * .classE{border : solid 1px black; padding : 0; margin-left : 5px; }
 *
 * .classF
 * {
 *     border     : none;
 *     padding    : 0;
 *     margin-top : 5px;
 *     text-align : left;
 * }
 *
 *
 */


/**
 * A class to manage a list of styles or a Style Class in CSS
 *
 * It is used as a css class in a stylesheet or as a style attribute of Html Element.
 *
 */
class StyleClass
{
    public $name;

    /**
     * Construct a style class, you can use it as a null constructor if you want.
     *
     * @param String $name the name of the class, only used in case of style sheet, inside html element you do not need it
     * @param Array $styles a style array, can be a css valid string, an array of element or a string indexed array.
     */
    function StyleClass($name = NULL, $styles = NULL)
    {
        // if name has { so it has style information
        if (stripos($name,'{') >0)
        {
            $this->setStyleClass($name);
        }
        else
        {
            $this->name = $name;

            if ($styles)
            {
                $this->setStyleClass($styles);
            }
        }
    }

    /**
    * Define the Class name
    *
    * @param $name the name of the class
    *
    */
    function setName($name)
    {
        $this->name = $name;
    }

    /**
    * Return the class name
    * @return the class name
    *
    */
    function getName()
    {
        return $this->name;
    }

    /**
    * Add one Element to StyleClass administrate if it previous exists in this class.
    *
    * This function has two ways of work:
    * 1. If you pass only $style , it consider that $style is a string and will separe it by : and will administrate the remaining spaces to pass only necesary information to the class propertys.
    * 2, If you pass $style and $value, it will consider the default behavior
    *
    * @param $style the definition of the style
    * @param $value the value of style
    */
    public function addStyle( $style, $value=NULL )
    {
    	//if has two parameters
        if ( isset( $style ) && isset( $value ) )
        {
            $style = trim( $style ); // trim remove spaces before and after string
            $value = trim( $value );
        }
        else if ( isset( $style ) )
        {
            $style = str_replace(';','', $style); //remove ; in the end
            $array  = explode(':', $style);
            $style = trim( $array[0] );
            $value = trim( $array[1] );
        }

        //set the style, adding it as an attribute of the object
        $this->$style = $value;
    }
    
    /**
    * Return the value of the Style
    *
    * @param $style the style to search
    * @return the value of the Style
    */
    public function getValue( $style )
    {
        return $this->$style;
    }

    /**
    * Remove an Element from the Class
    *
    * @param $style the style to remove
    *
    */
    public function removeStyle($style)
    {
        unset( $this->$style );
    }

    /**
    * Generate the Style element ready to html.
    * Note: all the values has initial an final space trimmed.
    *
    * @param $compact if compact is true it will result a string without spaces
    */
    public function renderElement($style, $value , $compact=false, $padlen=NULL)
    {
        if ( $style == 'name' || $style == 'styles' )
        {
            return '';
        }

        $style = trim( $style );
        $value = trim( $value );
        
        $style = $this->changeAttributeToCSS( $style );
        
        if ($padlen)
        {
            $style = str_pad($style, $padlen, ' ' , STR_PAD_RIGHT);
        }
        if (isset($this) && isset($value))
        {
            if (!$compact)
            {
                return  $style . ' : ' . $value . '; ';
            }
            else
            {
                return $style.':'.$value.';';
            }
        }
        else
        {
            return '';
        }
    }
    
    /**
     * Change a attribute of the class to CSS pattern
     * 
     * //TODO treat JS css names
     * 
     * Ex.: 
     * 
     * In class: backgroundColor
     * In css : background-color
     * 
     * 
     * @param string the name of the attribute
     * @return string the css class to it
     */
    function changeAttributeToCSS($string)
    {
        preg_match_all('([[:upper:]])',$string, $uppers ); //locate uppercase letters
        
        $array = preg_split('([[:upper:]])', $string); //explode by uppercase , losting upparcase letter
        
        //concatenate uppercase letter with explide string
        foreach ( $array as $line => $info )
        {
            $array[$line] = $uppers[0][$line-1] . $info;
        }
        
        //implode it with - setting case to lower
        return strtoLower( implode('-', $array) );
    }
    
    
    /**
     * Remove the comment in css pattern
     * 
     * Example: /* comment */ 
    /*
     * @param $string the string with comment to remove
     * @return String thw string without the comment
     */
    function removeComment( $string )
    {
		$patterns = '/\/\*.*\*\//';
		$replacements = '';
		
		return preg_replace( $patterns, $replacements, $string );	
    }
    
    
    
    
    /**
    * This generate the class ready to html, you can format it as you want
    *
    * @param $name  if has to put name or name (name and {} )
    * @param $newLine if is to use new lines inside styles and in name
    * @param $aling if is to align all styles or not
    * @param $indentSpaces the quantity of spaces to indent the styles declaration
    * @param $compact if the style declaration is to be compact or not
    * @return the style class string
    *
    */
    public function __toString($name = true, $newLine = false, $align = false , $indentSpaces = 0, $compact = true)
    {
        $styles = get_object_vars($this); //get all variables of object

        if ( $name && !$this->name)
        {
            return '';
        }

        $name = $this->name && $name ? true : false; //set $name false if it don't has name attribute
        
        if ($styles)
        {
            //initial name
            if ($name)
            {
                $result .= $this->name;
                
                if ($newLine)
                {
                    $result.= "\n{\n";
                }
                else
                {
                    $result .='{';
                }
            }
            //make align if needed
            if ($align)
            {
                $maxLen = 0;
                foreach ($styles as $line =>$info)
                {
                    if (strlen($info->style) > $maxLen)
                    {
                        $maxLen = strlen($info->style);
                    }
                }
            }
            //all styles
            foreach ($styles as $style => $value )
            {
                $elementString = $this->renderElement( $style, $value, $compact,$maxLen );

                $result.= str_repeat(' ', $indentSpaces). $elementString;

                if ($newLine)
                {
                    $result.= "\n";
                }
            }
            //close name
            if ($name)
            {
                if ($newLine)
                {
                    $result.= "}\n";
                }
                else
                {
                    $result .='}';
                }
            }
        }
        return $result;
    }

    /**
    * Return the StylesClass as a simles dimension array;
    * @return the StylesClass as a simles dimension array;
    */
    public function getArray()
    {
    	$result = get_object_vars($this);
    	
    	//remove the name 
    	unset( $result['name'] );
    	 
        return $result; 
    }

    /**
    * This functions convert an Array to StyleClass object, you can use the same sintax
    * of the result of getArray, or you can pass a string with CSS sintax (a:b;);
    *
    * @param array the array of styles
    */
    public function setStyleClass( $array=NULL )
    {
        if ($array)
        {
            //if is string .. convert to array
            if (!is_array($array) && is_string($array) )
            {
                $array = str_replace("\n", '', $array);
                
                //detect if has {, separate the name of the styles
                if (stripos($array,'{') >0)
                {
                    $temp = explode('{', $array);
                    $name = $temp[0];
                    $name = removeComments( $name );
                    $this->name = $name;
                    $array = str_replace('}', '', $temp[1]);
                }
                
                $array = explode(';', $array);
            }
            foreach ($array as $line => $info)
            {
                if (is_integer($line))
                {
                    $info = trim($info);
                    
                    if (!empty($info))
                    {
                        $this->addStyle($info);
                    }
                }
                else
                {
                    $this->addStyle($line, $info);
                }
            }
        }
    }

}


/**
* Element a Cascading Style Sheet
*
*/
class StyleSheet
{
    public $styleClass;

    /**
    * Constructs a Style Sheet passint it string or not
    *
    */
    public function StyleSheet($styleSheet=NULL)
    {
        if ($styleSheet)
        {
            $this->setStyleSheet($styleSheet);
        }
    }

    /**
    * Return the class called by name.
    *
    * @param $name the name of the
    * @return the StyleSheet as an Array
    **/
    public function getClass($className)
    {
        $styleClass = $this->styleClass;

        $className  = removeComments($className);

        if (is_array($styleClass))
        {
            foreach ($styleClass as $line => $info)
            {
                if ( $info->name == $className  )
                {
                    return $info;
                }
            }
        }
    }

    /**
    * Adds a new class to StyleSheet it can be a object of StyleClass clas or can be whatever StyleClass sintax.
    *
    * @param $class the classname using StyleClass construct sintax
    * @param $classString the string/array using StyleClass sintax
    */
    public function addClass($class, $classString=NULL)
    {
        if (is_object($class) && get_class($class) == 'StyleClass' )
        {
            $tempClass = $class;
        }
        else
        {
            $tempClass = new StyleClass($class, $classString);
        }

        $compareClass = $this->getClass( $tempClass->getName() );

        if ($compareClass)
        {
            $compareClass ->styles = $tempClass->styles;
        }
        else
        {
            $this->styleClass[] = $tempClass;
        }
    }

    /**
    * Remove a needed class using it name
    *
    * @param $className the name of the class to remove.
    */
    public function removeClass($className)
    {
        $styleClass = $this->styleClass;
        if (is_array($styleClass))
        {
            foreach ($styleClass as $line => $info)
            {
                if ($info->name == $className)
                {
                    unset($this->styleClass[$line]);
                }
            }
        }
    }

    /**
    * Define the style sheet using textual information.
    * This function dont suport objects or arrays.
    *
    * @param $styleSheet the string of the styleSheet
    */
    public function setStyleSheet($styleSheet)
    {
        $classes = explode('}', $styleSheet);
        if (is_array($classes) )
        {
            foreach ($classes as $line => $info)
            {
                $info = trim($info);
                if ( !empty($info) )
                {
                    $this->addClass($info);
                }
            }
        }
    }

    /**
    * Loads and css from url.
    * And set it to object, note that it support append of various files.
    *
    * @param $url the url to open
    */
    public function loadFromUrl($url)
    {
        $temp = file_get_contents($url, 'r');

        if ($temp)
        {
            $this->setStyleSheet($temp);
        }
        
        return $temp ? true : false;
    }

    /**
    * Save a content to file.
    *
    *
    */
    function saveToFile($filename, $content)
    {
        $handle = fopen($filename, 'w');
        fwrite($handle, $content. "\n");
        fclose($handle);
    }

    /**
    * Return the StyleSheet as an Array
    *
    * @return the StyleSheet as an Array
    */
    public function getArray()
    {
        $styleClass = $this->styleClass;
        if (is_array($styleClass) )
        {
            foreach ($styleClass as $line => $info)
            {
                $result[$info->getName()] = $info->getArray();
            }
        }
        return $result;
    }


    /**
    * This generate the class ready to html, you can format it as you want
    *
    * @param $name  if has to put name or name (name and {} )
    * @param $newLine if is to use new lines inside styles and in name
    * @param $aling if is to align all styles or not
    * @param $indentSpaces the quantity of spaces to indent the styles declaration
    * @param $compact if the style declaration is to be compact or not
    * @return the style class string
    *
    */
    public function __toString($name = true, $newLine = false, $align = false , $indentSpaces = 0, $compact = true)
    {
        $styleClass = $this->styleClass;

        $result = '';
        
        if ( $styleClass )
        {
            foreach ( $styleClass as $line => $info )
            {
                $result.=$info->__toString($name, $newLine, $align, $indentSpaces, $compact);
                
                if ($newLine)
                {
                    $result.= "\n";
                }
            }
        }

        return $result;
    }
}


function removeComments( $string )
{
    $string = trim($string);
    $string = str_replace("\n", '', $string);
    $string = str_replace("\r", '', $string);
    $string = str_replace("\t", '', $string);
    $string = preg_replace('/[\/*].*[*\/]/', '',$string );
    $string = str_replace('*', '', $string); 
    $string = trim($string);
    return $string;
}
?>
